import ObservableArray from "../observer/observable.array"
import funcUtil from "./func.util"
import Group from "./Group"
export type groupData = {
  visible: boolean
  roomId: string
  floorId: string
  type: number
  text: string
  created: Date
  modified: Date
  sid?: string
}
export default class Grouper {
  contents: ObservableArray<THREE.Vector3>
  groupInfo: groupData[]
  groupIndices: number[]
  groupInfoMap: Record<string, groupData>
  groupIndicesMap: Record<number, boolean>

  constructor(e) {
    this.contents = e
    this.groupInfo = []
    this.groupIndices = []
    this.groupInfoMap = {}
    this.groupIndicesMap = {}
  }
  startGroup(e: groupData) {
    const t = 0 === this.groupCount,
      i = this.contents.length !== this.groupIndices[this.groupCount - 1]
    if (t || i) {
      let t = e.sid
      if (!t) for (t = funcUtil.randomStr(); this.groupInfoMap.hasOwnProperty(t); ) t = funcUtil.randomStr()
      const i = Object.assign(Object.assign({}, e), { sid: t })
      this.groupInfo.push(i)
      this.groupInfoMap[t] = i
      this.groupIndices.push(this.contents.length)
      this.groupIndicesMap[this.contents.length] = !0
    }
    return this.groupIndices.length - 1
  }
  reset() {
    this.contents.atomic(() => {
      for (let e = this.contents.length - 1; e >= 0; --e) this.removeFromIdx(e)
      ;(this.groupIndices = []), (this.groupInfo = []), (this.groupIndicesMap = {})
    })
  }
  isStartIndex(e: number) {
    return !!this.groupIndicesMap[e]
  }
  *groups() {
    for (let e = 0; e < this.groupCount; ++e) yield this.getGroup(e)
  }
  *[Symbol.iterator]() {
    for (const e of this.contents) yield e
  }
  getGroupStartIndex(e: number) {
    return this.groupIndices[e]
  }
  getGroup(e: number) {
    const t = this.groupIndices[e],
      i = this.groupIndices[e + 1],
      n = isNaN(i) ? this.contents.length - 1 : i - 1
    return new Group(e, this.contents, t, n, Object.assign({}, this.groupInfo[e]))
  }
  getGroupById(e: string) {
    for (let t = 0; t < this.groupInfo.length; t++) if (e === this.groupInfo[t].sid) return this.getGroup(t)
    return false
  }
  indexOfGroup(e: (e: groupData) => boolean) {
    for (let t = 0; t < this.groupInfo.length; t++) if (e(this.groupInfo[t])) return t
    return -1
  }
  updateGroupInfo(e: number, t: groupData) {
    const i = this.groupInfo[e].sid
    delete this.groupInfoMap[i]
    const n = Object.assign({ sid: i }, t)
    this.groupInfo.splice(e, 1, n)
    this.groupInfoMap[i] = n
  }
  get groupCount() {
    return this.groupIndices.length
  }
  get length() {
    return this.contents.length
  }
  get(e: number) {
    return this.contents.get(e)
  }
  push(e: THREE.Vector3) {
    if (0 === this.groupCount) throw Error("Grouper: Error pushing points when we have no groups!")
    this.contents.push(e.clone())
    return this.contents.length
  }
  pop() {
    return this.removeFromIdx(this.contents.length - 1)
  }
  removeFromIdx(e: number) {
    if (e < this.contents.length && e >= 0 && this.contents.length >= 0) {
      const t = this.contents.get(e)
      this.contents.remove(e)
      this.removeEmptyGroups()
      return t
    }
  }
  removeEmptyGroups() {
    this.contents.length <= this.groupIndices[this.groupCount - 1] && this.removeGroup(this.groupCount - 1)
  }
  groupFromPointIndex(e: number) {
    if (0 > e || e >= this.length) return -1
    let t = this.groupCount - 1
    for (; e < this.groupIndices[t]; ) --t
    return t
  }
  removeGroup(e: number) {
    if (e > this.groupCount || e < 0) return
    const t = this.getGroup(e)
    if (0 === t.count) return
    const i = this.groupIndices[e]
    this.groupIndices.splice(e, 1), this.groupInfo.splice(e, 1), delete this.groupInfoMap[t.info.sid], delete this.groupIndicesMap[i]
    for (let i = e; i < this.groupCount; ++i) {
      const e = this.groupIndices[i],
        n = e - t.count
      this.groupIndices[i] = n
      delete this.groupIndicesMap[e]
      this.groupIndicesMap[n] = !0
    }
    this.contents.splice(t.startIndex, t.count)
  }
  update(e: number, t: THREE.Vector3) {
    this.contents.update(e, t.clone())
  }
  copy(e) {
    this.contents.atomic(() => {
      this.reset()
      for (const t of e) {
        this.startGroup(t.info)
        for (const e of t) this.push(e)
      }
    })
    return this
  }
  toString() {
    return `Grouper: { groupCount: ${this.groupCount}, length: ${this.length}, groups: [${[...this.groups()].map(e => `${e}`)}]}`
  }
}
